/*
 *    Copyright (c) 2018-2025, boss All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * Neither the name of the boss developer nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * Author: boss
 */
package com.hopes.bizz.close.flow.service.impl;

import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.hopes.bizz.close.common.constant.CloseModuleEnums;
import com.hopes.bizz.close.common.constant.ParkCloseProperty;
import com.hopes.bizz.close.common.constant.enums.ParkCloseModDictEnum;
import com.hopes.bizz.close.common.constant.enums.ParkCloseSysDictEnum;
import com.hopes.bizz.close.flow.dto.ClosedHazardGoodsStockRecordExcelDTO;
import com.hopes.bizz.close.flow.entity.ClosedHazardGoodsStockEntity;
import com.hopes.bizz.close.flow.entity.ClosedHazardGoodsStockRecordEntity;
import com.hopes.bizz.close.flow.mapper.ClosedHazardGoodsStockRecordMapper;
import com.hopes.bizz.close.flow.service.ClosedHazardGoodsStockRecordService;
import com.hopes.bizz.close.flow.service.ClosedHazardGoodsStockService;
import com.hopes.bizz.close.flow.vo.ClosedHazardGoodsStockRecordExcelVO;
import com.hopes.bizz.close.statistic.dto.StockEnterOuterDTO;
import com.hopes.bizz.close.statistic.vo.*;
import com.hopes.bizz.common.core.constant.CommonEnum;
import com.hopes.bizz.common.core.util.DateUtils;
import com.hopes.boss.admin.api.entity.SysDictItem;
import com.hopes.boss.admin.api.resolver.DictResolver;
import com.hopes.boss.common.core.exception.BizException;
import com.hopes.boss.common.entity.ValidateImportBO;
import com.hopes.boss.common.excel.vo.ErrorMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 企业危化品获取库存记录
 *
 * @author pig
 * @date 2024-02-06 10:01:10
 */
@Service
@RequiredArgsConstructor
public class ClosedHazardGoodsStockRecordServiceImpl extends ServiceImpl<ClosedHazardGoodsStockRecordMapper, ClosedHazardGoodsStockRecordEntity> implements ClosedHazardGoodsStockRecordService {

    private final ParkCloseProperty parkCloseProperty;
    private final ClosedHazardGoodsStockService closedHazardGoodsStockService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean save(ClosedHazardGoodsStockRecordEntity entity) {
        if (ObjUtil.isNull(entity.getDataTime())) {
            entity.setDataTime(Calendar.getInstance().getTime());
        }
        final boolean checkRes = validateHazardGoodsStack(entity);
        if (!checkRes) {
            throw new BizException("更新企业危险品库存数据异常！");
        }
        return SqlHelper.retBool(getBaseMapper().insert(entity));
    }

    private boolean validateHazardGoodsStack(ClosedHazardGoodsStockRecordEntity entity) {
        if (ObjUtil.isNull(entity.getGoodsId()) || StrUtil.isBlank(entity.getInOutDirection())) {
            throw new BizException("参数异常！");
        }
        final Long goodsId = entity.getGoodsId();
        final String inOutDirection = entity.getInOutDirection();
        final ClosedHazardGoodsStockEntity goodsStock = closedHazardGoodsStockService.getById(goodsId);
        if (ObjUtil.isNull(goodsStock)) {
            throw new BizException("获取企业常用危险品异常！");
        }
        if (CloseModuleEnums.InOutDirectionEnum.out.name().equals(inOutDirection) && entity.getInOutNum().compareTo(goodsStock.getStockNum()) > 0) {
            throw new BizException("危险品库存不足！");
        }
        final BigDecimal inOutNum = entity.getInOutNum();
        final BigDecimal inOutNumRes = CloseModuleEnums.InOutDirectionEnum.out.name().equals(inOutDirection) ? BigDecimal.ZERO.subtract(inOutNum) : inOutNum;
        return closedHazardGoodsStockService.lambdaUpdate()
                .eq(ClosedHazardGoodsStockEntity::getGoodsId, goodsId)
                .set(ClosedHazardGoodsStockEntity::getDataTime, entity.getDataTime())
                .set(ClosedHazardGoodsStockEntity::getStockNum, goodsStock.getStockNum().add(inOutNumRes)).update();
    }

    @Override
    public ValidateImportBO<ClosedHazardGoodsStockRecordExcelDTO> validateImport(List<ClosedHazardGoodsStockRecordExcelDTO> excelDTOList) {
        ValidateImportBO<ClosedHazardGoodsStockRecordExcelDTO> bo = new ValidateImportBO<>();
        List<ErrorMessage> errorMessageList = new ArrayList<>();
        List<ClosedHazardGoodsStockRecordExcelDTO> saveList = new ArrayList<>(excelDTOList);
        int successNum = excelDTOList.size();
        int failedNum = 0;

        bo.setErrorMessages(errorMessageList);
        bo.setSaveList(saveList);
        bo.setSuccessNum(successNum);
        bo.setFailedNum(failedNum);
        return bo;
    }

    @Override
    public ClosedHazardGoodsStockRecordEntity importDtoToEntity(ClosedHazardGoodsStockRecordExcelDTO excelDto) {
        ClosedHazardGoodsStockRecordEntity entity= new ClosedHazardGoodsStockRecordEntity();
        BeanUtils.copyProperties(excelDto, entity);
        return entity;
    }

    @Override
    public ClosedHazardGoodsStockRecordExcelVO exportEntityToVo(ClosedHazardGoodsStockRecordEntity entity) {
        ClosedHazardGoodsStockRecordExcelVO excelVO = new ClosedHazardGoodsStockRecordExcelVO();
        BeanUtils.copyProperties(entity, excelVO);
        return excelVO;
    }

    @Override
    public StockEnterOuterChartVO queryGoodsEnterOuterChart(StockEnterOuterDTO stockEnterOuterDTO) {
        final Long orgId = stockEnterOuterDTO.getOrgId();
        final Date endTime = DateUtils.parseDate(CommonEnum.StartEndEnum.END, stockEnterOuterDTO.getEndTime());
        final Date startTime = DateUtils.parseDate(CommonEnum.StartEndEnum.START, stockEnterOuterDTO.getStartTime());
        int calendar = Calendar.HOUR_OF_DAY;
        Function<Date, String> timeHandleMapper = DateUtils::limitHour;
        if (DateUtils.getIntervalDays(startTime, endTime) > parkCloseProperty.getGis().getChartHourToDay()) {
            calendar = Calendar.DAY_OF_YEAR;
            timeHandleMapper = DateUtils::limitDay;
        }
        final List<String> indexList = DateUtils.handlerDateTime(startTime, endTime, timeHandleMapper, calendar, 1);
        final List<StockEnterOuterVO> resList = indexList.stream().filter(StrUtil::isNotBlank).map(StockEnterOuterVO::new).collect(Collectors.toList());
        final List<StockEnterOuterVO> enterVOList = handleEnterOuterChartVOList(resList, timeHandleMapper, startTime, endTime, orgId, CloseModuleEnums.InOutDirectionEnum.in);
        final List<StockEnterOuterVO> outerVOList = handleEnterOuterChartVOList(resList, timeHandleMapper, startTime, endTime, orgId, CloseModuleEnums.InOutDirectionEnum.out);

        return new StockEnterOuterChartVO(enterVOList, outerVOList);
    }

    @Override
    public List<GoodsEnterOuterVO> queryGoodsEnterOuterTotalChart(StockEnterOuterDTO stockEnterOuterDTO) {
        final Long orgId = stockEnterOuterDTO.getOrgId();
        final Date endTime = DateUtils.parseDate(CommonEnum.StartEndEnum.END, stockEnterOuterDTO.getEndTime());
        final Date startTime = DateUtils.parseDate(CommonEnum.StartEndEnum.START, stockEnterOuterDTO.getStartTime());
        final LambdaQueryWrapper<ClosedHazardGoodsStockRecordEntity> queryWrapper = Wrappers.<ClosedHazardGoodsStockRecordEntity>lambdaQuery()
                .eq(ObjUtil.isNotNull(orgId), ClosedHazardGoodsStockRecordEntity::getOrgId, orgId)
                .gt(ClosedHazardGoodsStockRecordEntity::getDataTime, startTime).lt(ClosedHazardGoodsStockRecordEntity::getDataTime, endTime);
        final List<ClosedHazardGoodsStockRecordEntity> closedHazardGoodsStockRecordList = getBaseMapper().selectList(queryWrapper);
        final Set<Long> goodsIdSet = closedHazardGoodsStockRecordList.stream().map(ClosedHazardGoodsStockRecordEntity::getGoodsId).filter(ObjUtil::isNotNull).collect(Collectors.toSet());

        final List<GoodsEnterOuterVO> goodsEnterOuterVOList = new ArrayList<>();
        for (Long goodsId : goodsIdSet) {
            final Map<String, List<ClosedHazardGoodsStockRecordEntity>> listMap = closedHazardGoodsStockRecordList.stream()
                    .filter(e -> ObjUtil.isNotNull(e.getGoodsId()) && goodsId.equals(e.getGoodsId())).collect(Collectors.groupingBy(ClosedHazardGoodsStockRecordEntity::getInOutDirection));
            final List<ClosedHazardGoodsStockRecordEntity> enterList = listMap.getOrDefault(CloseModuleEnums.InOutDirectionEnum.in.name(), new ArrayList<>());
            final List<ClosedHazardGoodsStockRecordEntity> outerList = listMap.getOrDefault(CloseModuleEnums.InOutDirectionEnum.out.name(), new ArrayList<>());

            final double enterSum = enterList.stream().map(ClosedHazardGoodsStockRecordEntity::getInOutNum).mapToDouble(BigDecimal::doubleValue).sum();
            final double outerSum = outerList.stream().map(ClosedHazardGoodsStockRecordEntity::getInOutNum).mapToDouble(BigDecimal::doubleValue).sum();
            goodsEnterOuterVOList.add(new GoodsEnterOuterVO(goodsId, enterSum, outerSum));
        }
        return goodsEnterOuterVOList;
    }

    @Override
    public List<GoodsTypeNumVO> queryGoodsTypeEnterOuterChart(StockEnterOuterDTO stockEnterOuterDTO) {
        final Long orgId = stockEnterOuterDTO.getOrgId();
        final Date endTime = DateUtils.parseDate(CommonEnum.StartEndEnum.END, stockEnterOuterDTO.getEndTime());
        final Date startTime = DateUtils.parseDate(CommonEnum.StartEndEnum.START, stockEnterOuterDTO.getStartTime());
        final LambdaQueryWrapper<ClosedHazardGoodsStockRecordEntity> queryWrapper = Wrappers.<ClosedHazardGoodsStockRecordEntity>lambdaQuery()
                .eq(ObjUtil.isNotNull(orgId), ClosedHazardGoodsStockRecordEntity::getOrgId, orgId)
                .gt(ClosedHazardGoodsStockRecordEntity::getDataTime, startTime).lt(ClosedHazardGoodsStockRecordEntity::getDataTime, endTime);
        final List<ClosedHazardGoodsStockRecordEntity> closedHazardGoodsStockRecordList = getBaseMapper().selectList(queryWrapper);

        final List<GoodsTypeNumVO> resList = new ArrayList<>();
        final List<SysDictItem> sysDictItemList = DictResolver.getItemsByType(ParkCloseSysDictEnum.close_flow_bill_hazard_type);
        final Map<String, String> hazardGoodsMap = DictResolver.getItemsByType(ParkCloseModDictEnum.closed_hazard_goods).stream().collect(Collectors.toMap(SysDictItem::getCode, SysDictItem::getExt2));
        for (SysDictItem sysDictItem : sysDictItemList) {
            final Map<String, List<ClosedHazardGoodsStockRecordEntity>> listMap = closedHazardGoodsStockRecordList.stream()
                    .filter(e -> ObjUtil.isNotNull(e.getGoodsId()))
                    .filter(e -> StrUtil.isNotBlank(e.getInOutDirection()))
                    .filter(e -> sysDictItem.getCode().equals(hazardGoodsMap.get(e.getGoodsId().toString())))
                    .collect(Collectors.groupingBy(ClosedHazardGoodsStockRecordEntity::getInOutDirection));
            final double enterSum = listMap.getOrDefault(CloseModuleEnums.InOutDirectionEnum.in.name(), new ArrayList<>()).stream().map(ClosedHazardGoodsStockRecordEntity::getInOutNum).mapToDouble(BigDecimal::doubleValue).sum();
            final double outerSum = listMap.getOrDefault(CloseModuleEnums.InOutDirectionEnum.out.name(), new ArrayList<>()).stream().map(ClosedHazardGoodsStockRecordEntity::getInOutNum).mapToDouble(BigDecimal::doubleValue).sum();
            resList.add(new GoodsTypeNumVO(Integer.valueOf(sysDictItem.getCode()), enterSum, outerSum));
        }
        return resList;
    }

    @Override
    public List<GoodsTypeNumChartVO> queryGoodsTypeIndexChart(StockEnterOuterDTO stockEnterOuterDTO) {
        final Long orgId = stockEnterOuterDTO.getOrgId();
        final Date endTime = DateUtils.parseDate(CommonEnum.StartEndEnum.END, stockEnterOuterDTO.getEndTime());
        final Date startTime = DateUtils.parseDate(CommonEnum.StartEndEnum.START, stockEnterOuterDTO.getStartTime());
        int calendar = Calendar.HOUR_OF_DAY;
        Function<Date, String> timeHandleMapper = DateUtils::limitHour;
        if (DateUtils.getIntervalDays(startTime, endTime) > parkCloseProperty.getGis().getChartHourToDay()) {
            calendar = Calendar.DAY_OF_YEAR;
            timeHandleMapper = DateUtils::limitDay;
        }

        final LambdaQueryWrapper<ClosedHazardGoodsStockRecordEntity> queryWrapper = Wrappers.<ClosedHazardGoodsStockRecordEntity>lambdaQuery()
                .eq(ObjUtil.isNotNull(orgId), ClosedHazardGoodsStockRecordEntity::getOrgId, orgId)
                .gt(ClosedHazardGoodsStockRecordEntity::getDataTime, startTime).lt(ClosedHazardGoodsStockRecordEntity::getDataTime, endTime);
        final List<ClosedHazardGoodsStockRecordEntity> closedHazardGoodsStockRecordList = getBaseMapper().selectList(queryWrapper);

        final List<String> indexList = DateUtils.handlerDateTime(startTime, endTime, timeHandleMapper, calendar, 1);
        final List<GoodsTypeNumChartVO> resList = indexList.stream().filter(StrUtil::isNotBlank).map(GoodsTypeNumChartVO::new).collect(Collectors.toList());

        final List<SysDictItem> sysDictItemList = DictResolver.getItemsByType(ParkCloseSysDictEnum.close_flow_bill_hazard_type);
        final Map<String, String> hazardGoodsMap = DictResolver.getItemsByType(ParkCloseModDictEnum.closed_hazard_goods).stream().collect(Collectors.toMap(SysDictItem::getCode, SysDictItem::getExt2));

        for (GoodsTypeNumChartVO goodsTypeNumChartVO : resList) {
            final String index = goodsTypeNumChartVO.getIndex();
            goodsTypeNumChartVO.setIndex(index);
            goodsTypeNumChartVO.setGoodsTypeNumList(buildGoodsTypeNumList( index, sysDictItemList, hazardGoodsMap, closedHazardGoodsStockRecordList, timeHandleMapper, startTime, endTime));
        }
        return resList;
    }

    private List<GoodsTypeNumVO> buildGoodsTypeNumList(String index, List<SysDictItem> sysDictItemList, Map<String, String> hazardGoodsMap, List<ClosedHazardGoodsStockRecordEntity> recordList, Function<Date, String> timeHandleMapper, Date startTime, Date endTime) {
        final List<GoodsTypeNumVO> tempList = new ArrayList<>();
        for (SysDictItem sysDictItem : sysDictItemList) {
            final Map<String, List<ClosedHazardGoodsStockRecordEntity>> listMap = recordList.stream()
                    .filter(e -> ObjUtil.isNotNull(e.getGoodsId()))
                    .filter(e -> StrUtil.isNotBlank(e.getInOutDirection()))
                    .filter(e -> timeHandleMapper.apply(e.getDataTime()).equals(index))
                    .filter(e -> sysDictItem.getCode().equals(hazardGoodsMap.get(e.getGoodsId().toString())))
                    .collect(Collectors.groupingBy(ClosedHazardGoodsStockRecordEntity::getInOutDirection));
            final double enterSum = listMap.getOrDefault(CloseModuleEnums.InOutDirectionEnum.in.name(), new ArrayList<>()).stream().map(ClosedHazardGoodsStockRecordEntity::getInOutNum).mapToDouble(BigDecimal::doubleValue).sum();
            final double outerSum = listMap.getOrDefault(CloseModuleEnums.InOutDirectionEnum.out.name(), new ArrayList<>()).stream().map(ClosedHazardGoodsStockRecordEntity::getInOutNum).mapToDouble(BigDecimal::doubleValue).sum();
            tempList.add(new GoodsTypeNumVO(Integer.valueOf(sysDictItem.getCode()), enterSum, outerSum));
        }
        return tempList;
    }

    private List<StockEnterOuterVO> handleEnterOuterChartVOList(List<StockEnterOuterVO> resList, Function<Date, String> timeHandleMapper, Date startTime, Date endTime, Long orgId, CloseModuleEnums.InOutDirectionEnum inOutDirectionEnum) {
        final List<StockEnterOuterVO> tempList = new ArrayList<>();
        final LambdaQueryWrapper<ClosedHazardGoodsStockRecordEntity> queryWrapper = Wrappers.<ClosedHazardGoodsStockRecordEntity>lambdaQuery()
                .eq(ObjUtil.isNotNull(orgId), ClosedHazardGoodsStockRecordEntity::getOrgId, orgId)
                .eq(ClosedHazardGoodsStockRecordEntity::getInOutDirection, inOutDirectionEnum.name())
                .gt(ClosedHazardGoodsStockRecordEntity::getDataTime, startTime).lt(ClosedHazardGoodsStockRecordEntity::getDataTime, endTime);
        final List<ClosedHazardGoodsStockRecordEntity> closedHazardGoodsStockRecordList = getBaseMapper().selectList(queryWrapper);
        final Set<Long> goodsIdSet = closedHazardGoodsStockRecordList.stream().map(ClosedHazardGoodsStockRecordEntity::getGoodsId).filter(ObjUtil::isNotNull).collect(Collectors.toSet());

        for (StockEnterOuterVO enterOuterVO : resList) {
            final String index = enterOuterVO.getIndex();
            final Map<Long, List<ClosedHazardGoodsStockRecordEntity>> longListMap = closedHazardGoodsStockRecordList.stream()
                    .filter(e -> ObjUtil.isNotNull(e.getGoodsId()))
                    .filter(e -> ObjUtil.isNotNull(e.getDataTime()))
                    .filter(e -> timeHandleMapper.apply(e.getDataTime()).equals(index))
                    .collect(Collectors.groupingBy(ClosedHazardGoodsStockRecordEntity::getGoodsId));

            final List<GoodsNumVO> collect = goodsIdSet.stream().map(e -> {
                if (longListMap.containsKey(e)) {
                    return new GoodsNumVO(e, longListMap.get(e).stream().map(ClosedHazardGoodsStockRecordEntity::getInOutNum).filter(ObjUtil::isNotNull).mapToDouble(BigDecimal::doubleValue).sum());
                } else {
                    return new GoodsNumVO(e, 0D);
                }
            }).collect(Collectors.toList());
            enterOuterVO.setIndex(index);
            enterOuterVO.setGoodsNumVOList(collect);

            tempList.add(new StockEnterOuterVO(index, collect));
        }
        return tempList;
    }



}
